#1. Se cargan las librerias que se utilizaran para analizar los datos depositos_oink.csv
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import numpy as np
#2. Se carga el dataframe y se realiza una exploración inicial de los mismos:
df = pd.read_csv('D:/Mega/Personal/Varios/Procesos Seleccion/Coink/depositos_oinks.csv')
# para mayor facilidad, le cambio el nombre a la primera columna por "indice"
df.rename(columns={'Unnamed: 0':'indice',},inplace=True)
df
| indice | user_id | operation_value | operation_date | maplocation_name | user_createddate | |
|---|---|---|---|---|---|---|
| 0 | 0 | 0e52d550-ae23-407a-9b1f-6e5fb1f066ab | 273850.0 | 2022-01-14 13:07:56 | CC Plaza de las Américas - Plaza Mariposa | 2022-01-09 19:23:18.332689 |
| 1 | 1 | 975ed41e-d891-4c23-aeba-06363019d8e3 | 900.0 | 2022-02-06 13:11:25 | CC Plaza de las Américas - Plaza Mariposa | 2022-01-10 15:19:29.419075 |
| 2 | 2 | f9043545-dfc2-402f-a8c7-0a61f21cb719 | 1800.0 | 2022-02-23 14:01:56 | Universidad de los Andes - ML Piso 5 | 2021-08-25 12:44:48.524941 |
| 3 | 3 | 979ca8ad-9600-4a1f-81e9-e70c2f55cdc5 | 6500.0 | 2021-12-16 12:09:45 | CC Plaza de las Américas - Plaza Mariposa | 2021-07-17 17:11:16.766291 |
| 4 | 4 | 171db06e-2e4b-4542-a9c9-32028520fda4 | 150000.0 | 2022-01-25 12:00:18 | CC Plaza de las Américas - Plaza Mariposa | 2022-01-11 15:59:27.651994 |
| ... | ... | ... | ... | ... | ... | ... |
| 4340 | 4340 | c3d37131-76e8-4c4f-8e55-2778ab8817ad | 850.0 | 2021-12-26 13:29:06 | CC Los Molinos - Zona Montaña Nivel 2 | 2021-02-26 09:04:03.443362 |
| 4341 | 4341 | 40bc63c7-4ef1-420e-9e99-6c473b5fe5b1 | 1800.0 | 2022-01-23 14:32:37 | CC Los Molinos - Zona Montaña Nivel 2 | 2022-01-23 14:31:03.394729 |
| 4342 | 4342 | b99b3b89-8d45-4656-a950-c85f202897b1 | 3500.0 | 2021-12-06 19:33:24 | CC Plaza de las Américas - Plaza Mariposa | 2021-05-14 15:08:33.500127 |
| 4343 | 4343 | 443ffd46-0b2c-4383-9462-cf4b0519b6de | 126200.0 | 2022-01-24 11:03:38 | CC Los Molinos - Zona Montaña Nivel 2 | 2021-12-19 16:03:35.472917 |
| 4344 | 4344 | 0a8bbb74-a276-4434-895c-b30a94c04dee | 100.0 | 2022-02-12 11:58:57 | CC Plaza de las Américas - Plaza Mariposa | 2022-02-12 11:51:40.981069 |
4345 rows × 6 columns
#3 Se revisa el comportamiento de las diferentes variables
#3.1 user_id
res1=pd.DataFrame(df[['indice','user_id']].groupby('user_id').count())
res1.describe()
| indice | |
|---|---|
| count | 1656.000000 |
| mean | 2.623792 |
| std | 3.735081 |
| min | 1.000000 |
| 25% | 1.000000 |
| 50% | 1.000000 |
| 75% | 3.000000 |
| max | 61.000000 |
Existen 1656 usuarios unicos, de los cuales el 50% solo han realizado 1 deposito, otro 25% de los usuarios han realizado de 2 a 3 operaciones y el 25% restante, mas de tres operaciones.
#3.2 operation value
df.hist('operation_value',bins=100,range=[0, 300000], align='mid')
plt.title("Histograma Variable Operation Value")
Text(0.5, 1.0, 'Histograma Variable Operation Value')
df['op_ag']=pd.cut(df['operation_value'],bins=[0,5000,10000,15000,20000,25000,30000,35000,40000,45000,50000,1000000],
labels=['<5','5-10','10-15','15-20','20-25','25-30','30-35','35-40','40-45','45-50','>50'])
res2=pd.DataFrame(df[['indice','op_ag']].groupby('op_ag').count())
res2['par']=round(res2['indice']/sum(res2['indice'])*100,2)
res2['paracum']=res2['par'].cumsum()
plt.bar(res2.index,res2['par'])
plt.title('Participación de depósitos por rango')
plt.xlabel('Rango valor del depósito (Miles COP)')
plt.ylabel('% Participación')
res2
| indice | par | paracum | |
|---|---|---|---|
| op_ag | |||
| <5 | 2085 | 48.09 | 48.09 |
| 5-10 | 612 | 14.11 | 62.20 |
| 10-15 | 294 | 6.78 | 68.98 |
| 15-20 | 193 | 4.45 | 73.43 |
| 20-25 | 136 | 3.14 | 76.57 |
| 25-30 | 95 | 2.19 | 78.76 |
| 30-35 | 89 | 2.05 | 80.81 |
| 35-40 | 66 | 1.52 | 82.33 |
| 40-45 | 58 | 1.34 | 83.67 |
| 45-50 | 68 | 1.57 | 85.24 |
| >50 | 640 | 14.76 | 100.00 |
El 80% de los depósitos recibidos es menor a $ 35.000
# 3.3 operation date
# Inicialmente se generan a partir del operation_date, los campor anho_op, mes_op, dia_mes_op, dia_sem_op, hora_op y periodo_op
# se revisa el comportamiento de las operaciones para cada uno de esos valores
df['anho_op']=pd.DatetimeIndex(df['operation_date']).year
df['anho_op']=pd.to_numeric(df['anho_op'])
df['mes_op']=pd.DatetimeIndex(df['operation_date']).month.astype(str)
df['mes_op']=df['mes_op'].str.zfill(2)
df['periodo_op']=df['anho_op'].astype(str) + df['mes_op']
df['fecha']=pd.DatetimeIndex(df['operation_date']).date
df['dia_mes_op']=pd.DatetimeIndex(df['operation_date']).day
df['dia_semana_op']=pd.DatetimeIndex(df['operation_date']).weekday
df['hora_op']=pd.DatetimeIndex(df['operation_date']).hour
df['semana_op']=pd.DatetimeIndex(df['operation_date']).strftime("%W")
df['semana_op']=pd.to_numeric(df['semana_op'])
df['semana_op_a']=(df['anho_op']-2021)*52+df['semana_op']-46
df['fecha_cre_us']=pd.DatetimeIndex(df['user_createddate']).date
df.head()
| indice | user_id | operation_value | operation_date | maplocation_name | user_createddate | op_ag | anho_op | mes_op | periodo_op | fecha | dia_mes_op | dia_semana_op | hora_op | semana_op | semana_op_a | fecha_cre_us | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0e52d550-ae23-407a-9b1f-6e5fb1f066ab | 273850.0 | 2022-01-14 13:07:56 | CC Plaza de las Américas - Plaza Mariposa | 2022-01-09 19:23:18.332689 | >50 | 2022 | 01 | 202201 | 2022-01-14 | 14 | 4 | 13 | 2 | 8 | 2022-01-09 |
| 1 | 1 | 975ed41e-d891-4c23-aeba-06363019d8e3 | 900.0 | 2022-02-06 13:11:25 | CC Plaza de las Américas - Plaza Mariposa | 2022-01-10 15:19:29.419075 | <5 | 2022 | 02 | 202202 | 2022-02-06 | 6 | 6 | 13 | 5 | 11 | 2022-01-10 |
| 2 | 2 | f9043545-dfc2-402f-a8c7-0a61f21cb719 | 1800.0 | 2022-02-23 14:01:56 | Universidad de los Andes - ML Piso 5 | 2021-08-25 12:44:48.524941 | <5 | 2022 | 02 | 202202 | 2022-02-23 | 23 | 2 | 14 | 8 | 14 | 2021-08-25 |
| 3 | 3 | 979ca8ad-9600-4a1f-81e9-e70c2f55cdc5 | 6500.0 | 2021-12-16 12:09:45 | CC Plaza de las Américas - Plaza Mariposa | 2021-07-17 17:11:16.766291 | 5-10 | 2021 | 12 | 202112 | 2021-12-16 | 16 | 3 | 12 | 50 | 4 | 2021-07-17 |
| 4 | 4 | 171db06e-2e4b-4542-a9c9-32028520fda4 | 150000.0 | 2022-01-25 12:00:18 | CC Plaza de las Américas - Plaza Mariposa | 2022-01-11 15:59:27.651994 | >50 | 2022 | 01 | 202201 | 2022-01-25 | 25 | 1 | 12 | 4 | 10 | 2022-01-11 |
resanho1=pd.DataFrame(df[['indice','anho_op']].groupby('anho_op').count())
resanho2=pd.DataFrame(df[['operation_value','anho_op']].groupby('anho_op').sum())
resanho=pd.DataFrame(resanho1.merge(resanho2, left_on='anho_op', right_on='anho_op'))
resanho
| indice | operation_value | |
|---|---|---|
| anho_op | ||
| 2021 | 1477 | 51195450.0 |
| 2022 | 2868 | 99221300.0 |
ganho, axs = plt.subplots(1, 2, figsize=(9, 3), sharey=False)
plt.subplots_adjust(left=0.1,bottom=0.1,right=0.9,top=0.8, wspace=0.4, hspace=0.4)
ganho.suptitle('Comportamiento por año',color='blue', fontsize=15)
axs[0].set_xticks([2021,2022])
axs[0].set_title('Número de Depositos',fontsize=10)
axs[0].bar(resanho.index, resanho.indice)
axs[1].set_xticks([2021,2022])
axs[1].set_title('Valor de Depositos',fontsize=10)
axs[1].bar(resanho.index, resanho.operation_value)
axs[1].set_yticks(ticks=plt.yticks()[0], labels=plt.yticks()[0])
[<matplotlib.axis.YTick at 0x28b2d85cac0>, <matplotlib.axis.YTick at 0x28b2d85c340>, <matplotlib.axis.YTick at 0x28b2d855cd0>, <matplotlib.axis.YTick at 0x28b2d899070>, <matplotlib.axis.YTick at 0x28b2d899730>, <matplotlib.axis.YTick at 0x28b2d899e80>, <matplotlib.axis.YTick at 0x28b2d89f610>]
resper1=pd.DataFrame(df[['indice','periodo_op']].groupby('periodo_op').count())
resper2=pd.DataFrame(df[['operation_value','periodo_op']].groupby('periodo_op').sum())
resper=pd.DataFrame(resper1.merge(resper2, left_on='periodo_op', right_on='periodo_op'))
resper
| indice | operation_value | |
|---|---|---|
| periodo_op | ||
| 202111 | 212 | 5283950.0 |
| 202112 | 1265 | 45911500.0 |
| 202201 | 1422 | 53801100.0 |
| 202202 | 1446 | 45420200.0 |
gper, axs = plt.subplots(1, 2, figsize=(9, 3), sharey=False)
plt.subplots_adjust(left=0.1,bottom=0.1,right=0.9,top=0.8, wspace=0.4, hspace=0.4)
gper.suptitle('Comportamiento por periodo',color='blue', fontsize=15)
axs[0].set_title('Número de Depositos',fontsize=10)
axs[0].plot(resper.index, resper.indice,color="purple")
axs[1].set_title('Valor de Depositos',fontsize=10)
axs[1].plot(resper.index, resper.operation_value,color="purple")
axs[1].set_yticks(ticks=plt.yticks()[0], labels=plt.yticks()[0])
[<matplotlib.axis.YTick at 0x28b2d918700>, <matplotlib.axis.YTick at 0x28b2d910f40>, <matplotlib.axis.YTick at 0x28b2d9215e0>, <matplotlib.axis.YTick at 0x28b2d943970>, <matplotlib.axis.YTick at 0x28b2d9439d0>, <matplotlib.axis.YTick at 0x28b2d94c580>, <matplotlib.axis.YTick at 0x28b2d94ccd0>]
resfech1=pd.DataFrame(df[['indice','fecha']].groupby('fecha').count())
resfech2=pd.DataFrame(df[['operation_value','fecha']].groupby('fecha').sum())
resfech=pd.DataFrame(resfech1.merge(resfech2, left_on='fecha', right_on='fecha'))
gfech, axs = plt.subplots(1, 2, figsize=(9, 3), sharey=False)
plt.subplots_adjust(left=0.1,bottom=0.1,right=0.9,top=0.8, wspace=0.4, hspace=0.4)
gfech.suptitle('Evolución día a día Depósitos',color='blue', fontsize=15)
axs[0].set_title('Número de Depositos',fontsize=10)
axs[0].tick_params(axis='x', rotation=90)
axs[0].plot(resfech.index, resfech.indice,color="green")
axs[1].set_title('Valor de Depositos',fontsize=10)
axs[1].tick_params(axis='x', rotation=90)
axs[1].plot(resfech.index, resfech.operation_value,color="green")
axs[1].set_yticks(ticks=plt.yticks()[0], labels=plt.yticks()[0])
[<matplotlib.axis.YTick at 0x28b2d9d0a90>, <matplotlib.axis.YTick at 0x28b2d9d0310>, <matplotlib.axis.YTick at 0x28b2d9d7280>, <matplotlib.axis.YTick at 0x28b2d9f8e50>, <matplotlib.axis.YTick at 0x28b2da08550>, <matplotlib.axis.YTick at 0x28b2da08ca0>, <matplotlib.axis.YTick at 0x28b2da0c430>, <matplotlib.axis.YTick at 0x28b2da0cb80>]
resdm1=pd.DataFrame(df[['indice','dia_mes_op','periodo_op']].groupby(['dia_mes_op','periodo_op']).count()).reset_index()
resdm2=pd.DataFrame(df[['operation_value','dia_mes_op','periodo_op']].groupby(['dia_mes_op','periodo_op']).sum()).reset_index()
resdm=pd.DataFrame(resdm1.merge(resdm2, left_on=['dia_mes_op','periodo_op'], right_on=['dia_mes_op','periodo_op']))
plt.title('Comportamiento Número Depósitos por Día Mes')
g1=sns.lineplot(data=resdm, x="dia_mes_op", y="indice", hue="periodo_op")
plt.show()
plt.title('Comportamiento Valor Depósitos por Día Mes')
g2=sns.lineplot(data=resdm, x="dia_mes_op", y="operation_value", hue="periodo_op")
g2.set_yticks(ticks=plt.yticks()[0], labels=plt.yticks()[0])
plt.show()
resds1=pd.DataFrame(df[['indice','dia_semana_op','semana_op_a']].groupby(['dia_semana_op','semana_op_a']).count()).reset_index()
resds2=pd.DataFrame(df[['operation_value','dia_semana_op','semana_op_a']].groupby(['dia_semana_op','semana_op_a']).sum()).reset_index()
resds=pd.DataFrame(resds1.merge(resds2, left_on=['dia_semana_op','semana_op_a'], right_on=['dia_semana_op','semana_op_a']))
fig1 = px.box(resds, x="dia_semana_op", y="indice",title='Comportamiento Número Depósitos por Día de la Semana')
fig1.show()
fig2 = px.box(resds, x="dia_semana_op", y="operation_value",title='Comportamiento Valor Depósitos por Día de la Semana')
fig2.show()
resdh1=pd.DataFrame(df[['indice','hora_op']].groupby(['hora_op']).count()).reset_index()
resdh2=pd.DataFrame(df[['operation_value','hora_op']].groupby(['hora_op']).sum()).reset_index()
resdh=pd.DataFrame(resdh1.merge(resdh2, left_on='hora_op', right_on='hora_op'))
ghd, axs = plt.subplots(1, 2, figsize=(9, 3), sharey=False)
plt.subplots_adjust(left=0.1,bottom=0.1,right=0.9,top=0.8, wspace=0.4, hspace=0.4)
ghd.suptitle('Comportamiento por hora del día',color='blue', fontsize=15)
axs[0].set_title('Número de Depositos',fontsize=10)
axs[0].bar(resdh.hora_op, resdh.indice)
axs[1].set_title('Valor de Depositos',fontsize=10)
axs[1].bar(resdh.hora_op, resdh.operation_value)
axs[1].set_yticks(ticks=plt.yticks()[0], labels=plt.yticks()[0])
[<matplotlib.axis.YTick at 0x28b2f9003d0>, <matplotlib.axis.YTick at 0x28b2f8fbc10>, <matplotlib.axis.YTick at 0x28b2f906610>, <matplotlib.axis.YTick at 0x28b2f97a400>, <matplotlib.axis.YTick at 0x28b2f97ab50>]
#3.4 maplocation_name
res3=pd.DataFrame(df[['indice','maplocation_name']].groupby('maplocation_name').count()).reset_index()
res3=res3.sort_values(by=['indice'],ascending=False)
res4=pd.DataFrame(df[['operation_value','maplocation_name']].groupby('maplocation_name').sum()).reset_index()
res4=res4.sort_values(by=['operation_value'],ascending=False)
res4['par']=round(res4['operation_value']/sum(res4['operation_value'])*100,2)
gloc, axs = plt.subplots(1, 2, figsize=(9, 3), sharey=False)
plt.subplots_adjust(left=0.1,bottom=0.1,right=0.9,top=0.8, wspace=0.4, hspace=0.4)
gloc.suptitle('Comportamiento Depositos por Locación',color='blue', fontsize=15)
axs[0].set_title('Número de Depositos',fontsize=10)
axs[0].bar(res3.maplocation_name, res3.indice)
axs[0].tick_params(axis='x', rotation=90)
axs[1].set_title('Valor de Depositos',fontsize=10)
axs[1].bar(res4.maplocation_name, res4.operation_value)
axs[1].tick_params(axis='x', rotation=90)
axs[1].set_yticks(ticks=plt.yticks()[0], labels=plt.yticks()[0])
res4
| maplocation_name | operation_value | par | |
|---|---|---|---|
| 1 | CC Plaza de las Américas - Plaza Mariposa | 112241000.0 | 74.62 |
| 0 | CC Los Molinos - Zona Montaña Nivel 2 | 34597650.0 | 23.00 |
| 2 | Universidad de los Andes - ML Piso 5 | 3578100.0 | 2.38 |
El 74% del valor de los depósitos se ha recibido en el punto del CC Plaza de las Américas
#3.5 user_createddate (fecha_cre_us)
rescu=pd.DataFrame(df[['indice','fecha_cre_us']].groupby('fecha_cre_us').count()).reset_index()
rescu
plt.plot(rescu['fecha_cre_us'], rescu['indice'])
plt.title('Evolución de Creación de Nuevos Usuarios')
plt.xticks(rotation=90)
plt.show()
Dado que se desea evaluar el comportamiento de los diferentes usuarios, se transformará el dataframe para generar las siguientes estadisticas para cada uno de los usuarios:
A partir de estas variables, se realizará un modelo de clasificación de clientes con el algoritmo K-means
#4.1 Se genera el subtotal de n_operaciones
aux=pd.DataFrame(df[['user_id','indice']].groupby('user_id').count()).reset_index()
aux.rename(columns={'indice':'n_operaciones'},inplace=True)
dfc=aux
#4.2 Se genera el subtotal de v_operaciones
aux=pd.DataFrame(df[['user_id','operation_value']].groupby('user_id').sum()).reset_index()
aux.rename(columns={'operation_value':'v_operaciones'},inplace=True)
dfc=pd.DataFrame(dfc.merge(aux, left_on='user_id', right_on='user_id'))
#4.3 Se genera el valor promedio de las operaciones realizadas: v_prom_op
dfc['v_prom_op']=round(dfc['v_operaciones']/dfc['n_operaciones'],0)
#4.4 Para calcular el numero promedio de operaciones al mes: n_op_prom, se realizará primero el
# calculo de la antiguedad del cliente ant_usr_mes y ant_usr_a
df['ant_usr_mes']=(2022-pd.DatetimeIndex(df['user_createddate']).year)*12 + 3 - pd.DatetimeIndex(df['user_createddate']).month
aux=pd.DataFrame(df[['user_id','ant_usr_mes']].groupby('user_id').mean()).reset_index()
aux['ant_usr_a']=aux['ant_usr_mes']//12
dfc=pd.DataFrame(dfc.merge(aux, left_on='user_id', right_on='user_id'))
# 4.5 numero promedio de operaciones al mes y valor promedio de los depositos al mes,
# Se calcula con base en la fecha de creación del usuario
dfc['div']= [4 if x > 4 else x for x in dfc['ant_usr_mes']]
dfc['n_op_prom_mes']=round(dfc['n_operaciones']/dfc['div'],2)
dfc['v_op_prom_mes']=round(dfc['v_operaciones']/dfc['div'],0)
# 4.6 Se obtine lugar preferido para realizar los depósitos a partir del lugar con el numero maximo de depósitos
aux=pd.DataFrame(df[['user_id','maplocation_name','indice']].groupby(['maplocation_name','user_id']).count()).reset_index()
aux2=pd.DataFrame(aux.groupby(['user_id'])['indice'].max()).reset_index()
aux2=pd.DataFrame(aux2.merge(aux, left_on=['user_id','indice'], right_on=['user_id','indice']))
aux2.rename(columns={'maplocation_name':'lp'},inplace=True)
aux2=aux2[['user_id','lp']]
aux3=pd.DataFrame(aux2.groupby(['lp'])['user_id'].count()).reset_index()
# Se representa cada localización prefereida con números: CC Los Molinos = 1 / CC Plaza de las Américas = 2 /
# Universidad de los Andes = 3
aux3['lp_num']=range(1,4)
aux3=aux3[['lp','lp_num']]
aux2=pd.DataFrame(aux2.merge(aux3, left_on=['lp'], right_on=['lp']))
aux2=aux2[['user_id','lp_num']]
dfc=pd.DataFrame(dfc.merge(aux2, left_on='user_id', right_on='user_id'))
dfcf=dfc
dfcf.drop(['user_id','div'],axis='columns', inplace=True)
dfcf
| n_operaciones | v_operaciones | v_prom_op | ant_usr_mes | ant_usr_a | n_op_prom_mes | v_op_prom_mes | lp_num | |
|---|---|---|---|---|---|---|---|---|
| 0 | 3 | 44750.0 | 14917.0 | 4.0 | 0.0 | 0.75 | 11188.0 | 2 |
| 1 | 1 | 259000.0 | 259000.0 | 4.0 | 0.0 | 0.25 | 64750.0 | 1 |
| 2 | 4 | 375600.0 | 93900.0 | 12.0 | 1.0 | 1.00 | 93900.0 | 1 |
| 3 | 10 | 433450.0 | 43345.0 | 2.0 | 0.0 | 5.00 | 216725.0 | 1 |
| 4 | 1 | 1500.0 | 1500.0 | 3.0 | 0.0 | 0.33 | 500.0 | 2 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1651 | 1 | 50.0 | 50.0 | 1.0 | 0.0 | 1.00 | 50.0 | 1 |
| 1652 | 1 | 23000.0 | 23000.0 | 12.0 | 1.0 | 0.25 | 5750.0 | 2 |
| 1653 | 1 | 97800.0 | 97800.0 | 14.0 | 1.0 | 0.25 | 24450.0 | 2 |
| 1654 | 2 | 2500.0 | 1250.0 | 1.0 | 0.0 | 2.00 | 2500.0 | 1 |
| 1655 | 1 | 3300.0 | 3300.0 | 10.0 | 0.0 | 0.25 | 825.0 | 1 |
1656 rows × 8 columns
#5 Se revisa la colinealidad de las variables utilizando la matriz de correlación
col=dfcf.corr()
col
| n_operaciones | v_operaciones | v_prom_op | ant_usr_mes | ant_usr_a | n_op_prom_mes | v_op_prom_mes | lp_num | |
|---|---|---|---|---|---|---|---|---|
| n_operaciones | 1.000000 | 0.418901 | -0.008806 | -0.004857 | -0.011666 | 0.865811 | 0.369294 | -0.019337 |
| v_operaciones | 0.418901 | 1.000000 | 0.534348 | -0.015376 | -0.024884 | 0.345867 | 0.942932 | 0.041698 |
| v_prom_op | -0.008806 | 0.534348 | 1.000000 | -0.007882 | -0.022588 | -0.027263 | 0.572316 | 0.081743 |
| ant_usr_mes | -0.004857 | -0.015376 | -0.007882 | 1.000000 | 0.936319 | -0.175379 | -0.069441 | 0.394011 |
| ant_usr_a | -0.011666 | -0.024884 | -0.022588 | 0.936319 | 1.000000 | -0.123052 | -0.058556 | 0.366170 |
| n_op_prom_mes | 0.865811 | 0.345867 | -0.027263 | -0.175379 | -0.123052 | 1.000000 | 0.365210 | -0.033281 |
| v_op_prom_mes | 0.369294 | 0.942932 | 0.572316 | -0.069441 | -0.058556 | 0.365210 | 1.000000 | 0.047500 |
| lp_num | -0.019337 | 0.041698 | 0.081743 | 0.394011 | 0.366170 | -0.033281 | 0.047500 | 1.000000 |
Conclusión evaluación colinealidad: No se tendran en cuenta en el modelo de clustering las variables con colinealidad mayor a 0.85. De esta forma se tienen en cuenta para el modelo final las variables:
#6 Se cargan los paquetes requeridos para agrupar a los clientes en diferentes clases
from sklearn.preprocessing import StandardScaler
from scipy.spatial.distance import cdist
from sklearn.metrics import silhouette_score
from sklearn.cluster import DBSCAN
from sklearn.cluster import KMeans
from sklearn.cluster import AgglomerativeClustering
#7 Dado que el metodo de agrupación utiliza la distancia entre los datos para clasificarlos, primero se escalan las variables
#para que todas tengan la misma importancia en el modelo
dfcsc=dfcf[['v_prom_op','ant_usr_a','n_op_prom_mes','v_op_prom_mes','lp_num']]
scaler = StandardScaler()
scaler.fit(dfcsc)
dfcscsc=scaler.transform(dfcsc)
#8 Se identifica el número optimo de clusters para la agrupación
def dispersion(x, figure_name, max_k = 10, n_init = 10):
inercia = []
for k in range(1, max_k):
kmeans = KMeans(n_clusters = k, n_init = n_init).fit(x)
inercia.append(kmeans.inertia_)
plt.plot(range(1, max_k), inercia, 'bx-')
plt.xlabel('k')
plt.ylabel(u'Dispersión')
plt.title(figure_name)
dispersion(dfcscsc,'Dispersion Clusters')
plt.axvline(x=6,linestyle='--',color="lime",label="numero optimo de clusters")
plt.legend(shadow=True)
<matplotlib.legend.Legend at 0x28b371520d0>
#9.1 Se realiza la agrupración para 6 clusters
color_map = np.array(['b','g','r','c','m','y','k','j'])
kmp = pd.DataFrame(KMeans(n_clusters = 6, random_state = 1).fit_predict(dfcscsc))
kmp.rename(columns={0:'kmpred'},inplace=True)
dfcscp=pd.concat([dfcsc, kmp], axis=1)
rck=pd.DataFrame(dfcscp['kmpred'].value_counts()).reset_index()
rck['par']=round(rck['kmpred']/sum(rck['kmpred'])*100,2)
rck.rename(columns={'kmpred':'Cantidad'},inplace=True)
rck.rename(columns={'index':'kmpred'},inplace=True)
print(rck)
kmpred Cantidad par 0 0 703 42.45 1 4 529 31.94 2 1 242 14.61 3 2 96 5.80 4 3 71 4.29 5 5 15 0.91
fig, ax = plt.subplots(figsize=(5,5))
ax = sns.scatterplot(x='n_op_prom_mes',y='v_op_prom_mes',hue = 'kmpred',data = dfcscp,legend='full',
palette = {0:'red',1:'orange',2:'lightgreen',3:'lime',4:'gray',5:'green'})
ax.legend(loc='upper left')
<matplotlib.legend.Legend at 0x28b2dcd0eb0>
resag=pd.DataFrame(dfcscp[['v_prom_op','ant_usr_a','n_op_prom_mes','v_op_prom_mes','lp_num','kmpred']].groupby('kmpred').mean()).reset_index()
resag=pd.DataFrame(resag.merge(rck, left_on=['kmpred'], right_on=['kmpred']))
resag=resag.sort_values(by=['v_op_prom_mes'],ascending=False)
resag
| kmpred | v_prom_op | ant_usr_a | n_op_prom_mes | v_op_prom_mes | lp_num | Cantidad | par | |
|---|---|---|---|---|---|---|---|---|
| 5 | 5 | 496432.066667 | 0.133333 | 3.516667 | 899725.266667 | 2.000000 | 15 | 0.91 |
| 2 | 2 | 249055.020833 | 0.145833 | 0.713229 | 146488.541667 | 1.906250 | 96 | 5.80 |
| 3 | 3 | 16014.070423 | 0.056338 | 5.504789 | 79090.394366 | 1.521127 | 71 | 4.29 |
| 4 | 4 | 21155.396975 | 0.000000 | 0.816314 | 16021.930057 | 2.234405 | 529 | 31.94 |
| 0 | 0 | 13717.083926 | 0.116643 | 0.772290 | 11689.133713 | 1.000000 | 703 | 42.45 |
| 1 | 1 | 23813.814050 | 1.557851 | 0.538223 | 11358.735537 | 2.466942 | 242 | 14.61 |
En la base de datos de transacciones se identifican tres tipos de usuarios:
De acuerdo con lo anterior, se evaluan los usuarios de coink como buenos, si se clasifican dentro de los cluster 5, 2 y 3, regulares, si estan dentro del cluster 4 y malos si esta dentro de los cluster 0 y 1
Dado los costos de vinculación y administración de cada usuario, se espera del área de marketing estrategias para incrementar mes a mes el número y calidad de los usuarios. Está metrica podria verse así:
usbuenos= [random.randint((160 + x*30), (160 + x*70)) for x in range(1,13)]
usregulares= [random.randint((480 + x*10), (480 + x*15)) for x in range(1,13)]
usmalos = [random.randint((960 - (x*30)), (960 - (x*20))) for x in range(1,13)]
periodos =['202203','202204','202205','202206','202207','202208','202209','202210','202211','202212','202301','202302']
print(usmalos)
print(usregulares)
print(usbuenos)
plt.stackplot(periodos,usbuenos, usregulares,usmalos,labels = ["Buenos", "Regulares", "Malos"],colors=['green','gray','orange'])
plt.legend(loc = 'upper left')
plt.title('Evolución de Creación de Nuevos Usuarios')
plt.xticks(rotation=90)
plt.show()
[930, 917, 892, 846, 812, 813, 776, 722, 696, 722, 702, 611] [490, 506, 520, 526, 542, 545, 550, 576, 609, 628, 644, 601] [206, 263, 264, 365, 423, 377, 548, 585, 719, 513, 562, 618]
Tal como lo menciona el Blog del enlace y los manuales en la aplicación, el procedimiento sería:
Se identifican y seleccionan las variables que inciden de manera significativa en la satisfacción del trabajador
from scipy.stats import chi2_contingency
from scipy.stats import kruskal
#1 comportamiento variable dependiente
dfs = pd.read_excel('D:/Mega/Personal/Varios/Procesos Seleccion/Coink/info_satisfaccion_trabajo.xlsx')
dfs.describe()
dfs.head()
dfs['JobSatisfaction'].describe()
dfs['JobSatisfaction'].hist()
plt.title('Histograma de frecuencia JobSatisfaction')
Text(0.5, 1.0, 'Histograma de frecuencia JobSatisfaction')
Para los datos categoricos se validará la incidencia de la variable independiente sobre la variable JobSatisfaction usando la tabla de contingencia de frecuencias y la prueba Chi-cuadrado
Para los datos continuos grandes enteros o reales, se validará la incidencia sobre la variable dependiente con el test de Kruskal-Wallis
Los datos tipo texto con mas de 10 categorias seran eliminados del modelo
#1 Revisión de columnas tipo texto: se aplica la prueba chi2 para cada una de las variables de texto con respecto
# a JobSatisfaction
lvi = pd.DataFrame(dfs.dtypes).reset_index()
lvt = lvi[lvi[0] == 'object']
lvtlv = lvt['index'].tolist()
resvt = pd.DataFrame(columns=['variable', 'decision', 'valor p','numero de categorias'])
i=0
for v in lvtlv:
dfa = dfs[[v,'JobSatisfaction']]
tc = pd.crosstab(index=dfa[v],columns=dfa['JobSatisfaction'], margins=True)
ph = chi2_contingency(tc)[1]
resvt.loc[i,'variable']=v
if ph < 0.05:
resvt.loc[i,'decision']='incluir'
else:
resvt.loc[i,'decision']='excluir'
resvt.loc[i,'valor p']=ph
resvt.loc[i,'numero de categorias']=len(tc)-1
i=i+1
resvt
| variable | decision | valor p | numero de categorias | |
|---|---|---|---|---|
| 0 | Attrition | incluir | 0.025259 | 2 |
| 1 | BusinessTravel | excluir | 0.950231 | 3 |
| 2 | Department | excluir | 0.742606 | 3 |
| 3 | EducationField | excluir | 0.877563 | 6 |
| 4 | Gender | excluir | 0.95947 | 2 |
| 5 | JobRole | excluir | 0.993416 | 9 |
| 6 | MaritalStatus | excluir | 0.996563 | 3 |
| 7 | Over18 | excluir | 1.0 | 1 |
| 8 | OverTime | excluir | 0.884112 | 2 |
De las variables tipo texto solo se incluirá en el modelo la variable Attrition
tc = pd.crosstab(columns=dfs['Attrition'],index=dfs['JobSatisfaction'], margins=False, normalize='index')
barplot = tc.plot.bar(rot=0,color=['b','gray'],stacked=True)
#2 Revisión de columnas tipo numero: se aplica la prueba chi2 para las variables numericas con menos de 10 valores, dado que
# estan representando variables categoricas
# para las variables numericas con un rango mayor de 10, se aplica la pruebaa Kruskal-Wallis
dvn=dfs.describe().transpose()
dvn['rango']=dvn['max']-dvn['min']
dvn['eval_chi2']= ['si' if x < 11 else 'no' for x in dvn['rango']]
dvn
| count | mean | std | min | 25% | 50% | 75% | max | rango | eval_chi2 | |
|---|---|---|---|---|---|---|---|---|---|---|
| Age | 1470.0 | 36.923810 | 9.135373 | 18.0 | 30.00 | 36.0 | 43.00 | 60.0 | 42.0 | no |
| DailyRate | 1470.0 | 802.485714 | 403.509100 | 102.0 | 465.00 | 802.0 | 1157.00 | 1499.0 | 1397.0 | no |
| DistanceFromHome | 1470.0 | 9.192517 | 8.106864 | 1.0 | 2.00 | 7.0 | 14.00 | 29.0 | 28.0 | no |
| Education | 1470.0 | 2.912925 | 1.024165 | 1.0 | 2.00 | 3.0 | 4.00 | 5.0 | 4.0 | si |
| EmployeeCount | 1470.0 | 1.000000 | 0.000000 | 1.0 | 1.00 | 1.0 | 1.00 | 1.0 | 0.0 | si |
| EmployeeNumber | 1470.0 | 1024.865306 | 602.024335 | 1.0 | 491.25 | 1020.5 | 1555.75 | 2068.0 | 2067.0 | no |
| EnvironmentSatisfaction | 1470.0 | 2.721769 | 1.093082 | 1.0 | 2.00 | 3.0 | 4.00 | 4.0 | 3.0 | si |
| HourlyRate | 1470.0 | 65.891156 | 20.329428 | 30.0 | 48.00 | 66.0 | 83.75 | 100.0 | 70.0 | no |
| JobInvolvement | 1470.0 | 2.729932 | 0.711561 | 1.0 | 2.00 | 3.0 | 3.00 | 4.0 | 3.0 | si |
| JobLevel | 1470.0 | 2.063946 | 1.106940 | 1.0 | 1.00 | 2.0 | 3.00 | 5.0 | 4.0 | si |
| JobSatisfaction | 1470.0 | 2.728571 | 1.102846 | 1.0 | 2.00 | 3.0 | 4.00 | 4.0 | 3.0 | si |
| MonthlyIncome | 1470.0 | 6502.931293 | 4707.956783 | 1009.0 | 2911.00 | 4919.0 | 8379.00 | 19999.0 | 18990.0 | no |
| MonthlyRate | 1470.0 | 14313.103401 | 7117.786044 | 2094.0 | 8047.00 | 14235.5 | 20461.50 | 26999.0 | 24905.0 | no |
| NumCompaniesWorked | 1470.0 | 2.693197 | 2.498009 | 0.0 | 1.00 | 2.0 | 4.00 | 9.0 | 9.0 | si |
| PercentSalaryHike | 1470.0 | 15.209524 | 3.659938 | 11.0 | 12.00 | 14.0 | 18.00 | 25.0 | 14.0 | no |
| PerformanceRating | 1470.0 | 3.153741 | 0.360824 | 3.0 | 3.00 | 3.0 | 3.00 | 4.0 | 1.0 | si |
| RelationshipSatisfaction | 1470.0 | 2.712245 | 1.081209 | 1.0 | 2.00 | 3.0 | 4.00 | 4.0 | 3.0 | si |
| StandardHours | 1470.0 | 80.000000 | 0.000000 | 80.0 | 80.00 | 80.0 | 80.00 | 80.0 | 0.0 | si |
| StockOptionLevel | 1470.0 | 0.793878 | 0.852077 | 0.0 | 0.00 | 1.0 | 1.00 | 3.0 | 3.0 | si |
| TotalWorkingYears | 1470.0 | 11.279592 | 7.780782 | 0.0 | 6.00 | 10.0 | 15.00 | 40.0 | 40.0 | no |
| TrainingTimesLastYear | 1470.0 | 2.799320 | 1.289271 | 0.0 | 2.00 | 3.0 | 3.00 | 6.0 | 6.0 | si |
| WorkLifeBalance | 1470.0 | 2.761224 | 0.706476 | 1.0 | 2.00 | 3.0 | 3.00 | 4.0 | 3.0 | si |
| YearsAtCompany | 1470.0 | 7.008163 | 6.126525 | 0.0 | 3.00 | 5.0 | 9.00 | 40.0 | 40.0 | no |
| YearsInCurrentRole | 1470.0 | 4.229252 | 3.623137 | 0.0 | 2.00 | 3.0 | 7.00 | 18.0 | 18.0 | no |
| YearsSinceLastPromotion | 1470.0 | 2.187755 | 3.222430 | 0.0 | 0.00 | 1.0 | 3.00 | 15.0 | 15.0 | no |
| YearsWithCurrManager | 1470.0 | 4.123129 | 3.568136 | 0.0 | 2.00 | 3.0 | 7.00 | 17.0 | 17.0 | no |
lvnc = dvn[(dvn['eval_chi2'] == 'si') & (dvn['rango']>0)]
lvncv =lvnc.index.tolist()
lvncv.remove('JobSatisfaction')
resvnc = pd.DataFrame(columns=['variable', 'decision', 'valor p','numero de categorias'])
i=0
for v in lvncv:
dfa = dfs[[v,'JobSatisfaction']]
tc = pd.crosstab(index=dfa[v],columns=dfa['JobSatisfaction'], margins=True)
ph = chi2_contingency(tc)[1]
resvnc.loc[i,'variable']=v
if ph < 0.05:
resvnc.loc[i,'decision']='incluir'
else:
resvnc.loc[i,'decision']='excluir'
resvnc.loc[i,'valor p']=ph
resvnc.loc[i,'numero de categorias']=len(tc)-1
i=i+1
resvnc
| variable | decision | valor p | numero de categorias | |
|---|---|---|---|---|
| 0 | Education | excluir | 0.876106 | 5 |
| 1 | EnvironmentSatisfaction | excluir | 0.993924 | 4 |
| 2 | JobInvolvement | excluir | 0.964259 | 4 |
| 3 | JobLevel | excluir | 0.998507 | 5 |
| 4 | NumCompaniesWorked | excluir | 0.985831 | 10 |
| 5 | PerformanceRating | excluir | 0.855829 | 2 |
| 6 | RelationshipSatisfaction | excluir | 0.999844 | 4 |
| 7 | StockOptionLevel | excluir | 0.999904 | 4 |
| 8 | TrainingTimesLastYear | excluir | 0.866542 | 7 |
| 9 | WorkLifeBalance | excluir | 0.980587 | 4 |
Se excluyen todas las variables numericas enteras dado que ninguna afecta significativamente el JobSatisfaction
tc = pd.crosstab(columns=dfs['PerformanceRating'],index=dfs['JobSatisfaction'], margins=False, normalize='index')
barplot = tc.plot.bar(rot=0,color=['b','gray'],stacked=True)
lvne = dvn[dvn['eval_chi2'] == 'no']
lvnev =lvne.index.tolist()
resvne = pd.DataFrame(columns=['variable', 'decision', 'valor p'])
i=0
for v in lvnev:
dfa = dfs[[v,'JobSatisfaction']]
g1=dfa[dfa['JobSatisfaction'] == 1][v]
g2=dfa[dfa['JobSatisfaction'] == 2][v]
g3=dfa[dfa['JobSatisfaction'] == 3][v]
g4=dfa[dfa['JobSatisfaction'] == 4][v]
ph= kruskal(g1,g2,g3,g4)[1]
resvne.loc[i,'variable']=v
if ph < 0.05:
resvne.loc[i,'decision']='incluir'
else:
resvne.loc[i,'decision']='excluir'
resvne.loc[i,'valor p']=ph
i=i+1
resvne
| variable | decision | valor p | |
|---|---|---|---|
| 0 | Age | excluir | 0.9564 |
| 1 | DailyRate | excluir | 0.149262 |
| 2 | DistanceFromHome | excluir | 0.69568 |
| 3 | EmployeeNumber | excluir | 0.331907 |
| 4 | HourlyRate | incluir | 0.039766 |
| 5 | MonthlyIncome | excluir | 0.858179 |
| 6 | MonthlyRate | excluir | 0.676142 |
| 7 | PercentSalaryHike | excluir | 0.427498 |
| 8 | TotalWorkingYears | excluir | 0.657358 |
| 9 | YearsAtCompany | excluir | 0.930758 |
| 10 | YearsInCurrentRole | excluir | 0.995135 |
| 11 | YearsSinceLastPromotion | excluir | 0.916826 |
| 12 | YearsWithCurrManager | excluir | 0.727609 |
Solo se considerará en el modelo la variable HourlyRate, la cual es la única que incide significativamente en la satisfaccion del empleado
g1=pd.DataFrame(dfs[dfs['JobSatisfaction'] == 1]['HourlyRate']).reset_index()
g2=pd.DataFrame(dfs[dfs['JobSatisfaction'] == 2]['HourlyRate']).reset_index()
g3=pd.DataFrame(dfs[dfs['JobSatisfaction'] == 3]['HourlyRate']).reset_index()
g4=pd.DataFrame(dfs[dfs['JobSatisfaction'] == 4]['HourlyRate']).reset_index()
fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2,)
plt.subplots_adjust(left=0.1,bottom=0.1,right=0.9,top=0.8, wspace=0.4, hspace=0.4)
fig.suptitle('Histogramas JobSatisfaction vs HourlyRate',color='blue', fontsize=15)
axs[1].set_title('Valor de Depositos',fontsize=10)
ax0.hist(g1.HourlyRate, 10, density=True, histtype='bar', color='red')
ax0.set_title('JobSatisfaction = 1',fontsize=10)
ax1.hist(g2.HourlyRate, 10, density=True, histtype='bar', color='blue')
ax1.set_title('JobSatisfaction = 2',fontsize=10)
ax2.hist(g3.HourlyRate, 10, density=True, histtype='bar', color='lime')
ax2.set_title('JobSatisfaction = 3',fontsize=10)
ax3.hist(g4.HourlyRate, 10, density=True, histtype='bar', color='green')
ax3.set_title('JobSatisfaction = 4',fontsize=10)
Text(0.5, 1.0, 'JobSatisfaction = 4')
Se realizan los modelos 1. Knn y 2. Arbol de Decisión. Para el modelo knn se remplazan los valores de Attrition (yes,no) por 1 y 0, dado que ese modelo clasifica según distancias
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.ensemble import RandomForestClassifier
from numpy import *
# 1. Se seleccionan las columnas y se divide el dataframe en subconjunto de entrenamiento y prueba
dfs['Attrition_aj']=[1 if x == 'Yes' else 0 for x in dfs['Attrition']]
x = dfs[['Attrition_aj','HourlyRate']]
scaler = StandardScaler()
scaler.fit(x)
xs=scaler.transform(x)
y = dfs[['JobSatisfaction']]
xs_train, xs_test, y_train, y_test = train_test_split(xs, y,random_state=29)
#2 Entrenamiento del modelo y evaluación
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(xs_train,y_train)
y_pred=knn.predict(xs_test)
cf=classification_report(y_test,y_pred)
print(cf)
precision recall f1-score support
1 0.22 0.22 0.22 63
2 0.22 0.23 0.22 71
3 0.31 0.41 0.35 109
4 0.38 0.26 0.31 125
accuracy 0.29 368
macro avg 0.28 0.28 0.27 368
weighted avg 0.30 0.29 0.29 368
D:\anaconda3\lib\site-packages\sklearn\neighbors\_classification.py:198: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel(). D:\anaconda3\lib\site-packages\sklearn\neighbors\_classification.py:228: FutureWarning: Unlike other reduction functions (e.g. `skew`, `kurtosis`), the default behavior of `mode` typically preserves the axis it acts along. In SciPy 1.11.0, this behavior will change: the default value of `keepdims` will become False, the `axis` over which the statistic is taken will be eliminated, and the value None will no longer be accepted. Set `keepdims` to True or False to avoid this warning.
El modelo cuenta con una precisión del 30%
#3 Modelo ramdom forest
x = dfs[['Attrition_aj','HourlyRate']]
y = dfs[['JobSatisfaction']]
x_train, x_test, y_train, y_test = train_test_split(x, y,random_state=86)
rf=RandomForestClassifier(n_estimators=20,random_state = 123)
rf.fit(x_train,y_train)
y_pred=rf.predict(x_test)
cf=classification_report(y_test,y_pred)
print(cf)
precision recall f1-score support
1 0.26 0.16 0.20 63
2 0.23 0.10 0.14 71
3 0.37 0.50 0.42 111
4 0.41 0.50 0.45 123
accuracy 0.36 368
macro avg 0.32 0.31 0.30 368
weighted avg 0.34 0.36 0.34 368
C:\Users\macke\AppData\Local\Temp\ipykernel_18436\1101891941.py:7: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
El modelo cuenta con una precisión del 34%
Para monitorear el modelo en producción, se deben comparar los resultados reales del JobSatisfaction vs los resultados que predice el modelo para obtener las medidas de precisión y recuperación.
Si en la validación de los resultados reales vs los predichos por el modelo, el valor de la preción o recuperación bajan de forma significativa, se debe evaluar ajustar o entrenar el modelo nuevamente.
No obstante, dada la baja precisión de los dos modelos realizados, deberia considerarse la identificación de otras variables significativamente incidentes en la satisfaccion del trabajador o cambiar la escala de valoración de 1 a 10